package com.geeksss.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.listener.ReadListener;
import com.geeksss.excel.constant.ExcelConstant;
import com.geeksss.excel.model.UserInfoModel;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 用户信息，读取监听器。
 */
@Slf4j
public class UserInfoReadListener implements ReadListener<UserInfoModel> {
    
    /**
     * 批次大小。
     * 这个变量用于控制每次处理数据的数量。
     */
    private final int batchSize;
    
    /**
     * 缓存数据。
     * 一个列表，用于临时存储读取的数据直到达到批次大小。
     */
    private final List<UserInfoModel> cacheData;
    
    /**
     * 条件函数。
     * 一个Predicate接口的实例，用于判断数据是否满足某些条件。
     */
    private final Predicate<UserInfoModel> predicate;
    
    /**
     * 消费函数。
     * 一个Consumer接口的实例，用于处理满足条件的数据。
     */
    private final Consumer<List<UserInfoModel>> consumer;
    
    /**
     * 构造函数。
     * 使用默认的批次大小初始化监听器。
     *
     * @param predicate 条件函数。
     * @param consumer  消费函数。
     */
    public UserInfoReadListener(Predicate<UserInfoModel> predicate, Consumer<List<UserInfoModel>> consumer) {
        this(ExcelConstant.BATCH_SIZE, predicate, consumer);
    }
    
    /**
     * 构造函数。
     * 初始化监听器，指定批次大小。
     *
     * @param batchSize 批次大小。
     * @param predicate 条件函数。
     * @param consumer  消费函数。
     */
    public UserInfoReadListener(int batchSize, Predicate<UserInfoModel> predicate, Consumer<List<UserInfoModel>> consumer) {
        this.batchSize = batchSize;
        this.predicate = predicate;
        this.consumer = consumer;
        this.cacheData = new ArrayList<>(this.batchSize);
    }
    
    /**
     * 读取数据。
     * 当数据被读取时调用此方法，根据条件函数过滤数据，并在达到批次大小时调用消费函数。
     *
     * @param data    数据。
     * @param context 上下文。
     */
    @Override
    public void invoke(UserInfoModel data, AnalysisContext context) {
        if (!this.predicate.test(data)) {
            return;
        }
        this.cacheData.add(data);
        if (this.cacheData.size() >= this.batchSize) {
            this.consumer.accept(this.cacheData);
            this.cacheData.clear();
        }
    }
    
    /**
     * 异常处理。
     *
     * 当在解析Excel数据时遇到异常，此方法会被调用。
     * 它主要用于处理数据转换异常，并记录错误信息。
     * 如果异常不属于Excel数据转换异常类型，则直接抛出该异常。
     *
     * @param exception 遇到的异常，具体类型为ExcelDataConvertException或其子类。
     * @param context   解析上下文，用于获取解析过程中的相关信息。
     * @throws Exception 如果异常不属于Excel数据转换异常类型，则抛出原异常。
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        // 判断异常是否为Excel数据转换异常
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            // 获取出错的行索引、列索引和单元格数据
            int rowIndex = excelDataConvertException.getRowIndex();
            int columnIndex = excelDataConvertException.getColumnIndex();
            String cellData = excelDataConvertException.getCellData().getStringValue();
            // 记录错误信息
            String reason = exception.getMessage();
            log.error("第{}行，第{}列，单元格[{}]，解析异常：{}", rowIndex + 1, columnIndex + 1, cellData, reason);
            // 抛出异常，停止进一步的解析
            throw new ExcelAnalysisStopException();
        }
        // 如果异常不属于Excel数据转换异常，则直接抛出
        throw exception;
    }
    
    
    /**
     * 解析完成。
     * 当所有数据都被解析后调用此方法，确保任何剩余的数据也被处理。
     *
     * @param context 上下文。
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (this.cacheData.size() > 0) {
            this.consumer.accept(this.cacheData);
            this.cacheData.clear();
        }
    }

}
